// Lindsay Gilmour
// Adam Lusch
// Dan Niceswanger
//
// Computer Graphics Project 2:
// Teapot racing!

#include "PhysicsWorld.h"

const int PhysicsWorld::LAP_COUNT = 3;
int PhysicsWorld::RACE_MODE = 0;

#include <iomanip>

// Light position so it can be moved with the camera
GLfloat spotPosit0[] = {10.0f, 60.0f, 10.0f, 0.0f};

PhysicsWorld::PhysicsWorld()
{	
	worldAABB.lowerBound.Set(-1000.0f, -1000.0f);
	worldAABB.upperBound.Set(1000.0f, 1000.0f);

	doSleep = true;
	b2Vec2 gravity(0.0f, 0.0f);

	world = new b2World(worldAABB, gravity, doSleep);
	//debug.SetFlags(b2DebugDraw::e_shapeBit);
 	//world->SetDebugDraw(&debug);
 	
 	player = 0;
 	ghost = 0;
 	track = 0;
	
	currentCheckpoint = 1;
	currentLap = 1;
	
	isPlaying = false;
	rotate = 0.0;
	
	//collisions = new CollisionHandler;
	//world->SetContactListener(collisions);
}

PhysicsWorld::~PhysicsWorld()
{
	delete world;
	//delete collisions;
	
	delete player;
	delete ghost;
	delete track;
	
	Teapot::cleanup();
	Track::cleanup();
}

// Draw text at a specific location (assumes orthographic projection)
void PhysicsWorld::drawText(float32 scrPosX, float32 scrPosY, string text)
{
	glPushMatrix();
		glPushAttrib(GL_ALL_ATTRIB_BITS);
		glDisable(GL_LIGHTING);
		glTranslatef(scrPosX, -scrPosY, 0.0);
		glScalef(0.0075f, 0.01f, 0.01f);
		
		for (unsigned int i = 0; i < text.size(); i++)
		{
			glutStrokeCharacter(GLUT_STROKE_MONO_ROMAN, text[i]);
		}
		glPopAttrib();
	glPopMatrix();
}

void PhysicsWorld::init()
{
	Teapot::setup();
	Track::setup();
	initCheckpoints();
}

// coordinates for Checkpoints
void PhysicsWorld::initCheckpoints()
{
	checkpoints.clear();
	
	checkpoints.push_back(Checkpoint(-9.0, -12.25, -9.0, -9.75));
	checkpoints.push_back(Checkpoint(2.0, -12.25, 2.0, -9.75));
	checkpoints.push_back(Checkpoint(18.35, -7.97, 15.65, -7.97));
	checkpoints.push_back(Checkpoint(12.35, -1.95, 9.65, -1.95));
	checkpoints.push_back(Checkpoint(19.23, 0.76, 17.15, 2.68));
	checkpoints.push_back(Checkpoint(19.29, 11.26, 17.29, 9.2));
	checkpoints.push_back(Checkpoint(7.40, 12.27, 7.40, 9.58));
	checkpoints.push_back(Checkpoint(1.85, -0.72, 1.85, -3.28));
	checkpoints.push_back(Checkpoint(-4.2, -0.18, -6.32, -2.2));
	checkpoints.push_back(Checkpoint(-5.85, 10.25, -7.77, 8.18));
	checkpoints.push_back(Checkpoint(-16.23, 10.24, -14.21, 8.27));
	checkpoints.push_back(Checkpoint(-19.35, 2.0, -16.71, 2.0));
	checkpoints.push_back(Checkpoint(-16.69, -11.67, -14.74, -9.63));
	
	checkpoints[0].doDraw = false;
}

// is the player crossing a checkpoint?
void PhysicsWorld::checkpointStatus()
{
	Checkpoint c = checkpoints[currentCheckpoint];
	
	// case 1
	if (c.x1 == c.x2)
	{
		// within reasonable x range
		// within exact z range
		if (c.x1-0.10 <= player->getX() && player->getX() <= c.x1+0.10 && player->getZ() >= min(c.z1, c.z2) && player->getZ() <= max(c.z1, c.z2))
		{
			updateCheckpoint();
		}
	}
	// case 2
	else if (c.z1 == c.z2)
	{
		// within reasonable z range
		// within exact x range
		if (c.z1-0.10 <= player->getZ() && player->getZ() <= c.z1+0.10 && player->getX() >= min(c.x1, c.x2) && player->getX() <= max(c.x1, c.x2))
		{
			updateCheckpoint();
		}
	}
	// case 3
	else if (player->getX() >= min(c.x1, c.x2) && player->getX() <= max(c.x1, c.x2))
	{
		float m = (c.z1 - c.z2)/(c.x1 - c.x2);
		float b = c.z1 - m*c.x1;
		
		float z_approx = m*player->getX() + b;
		
		// reasonably close to the slope of the line connecting the two posts
		if (z_approx-0.10 <= player->getZ() && player->getZ() <= z_approx+0.10)
		{	
			updateCheckpoint();
		} 
	}
}

// increment checkpoint, possibly increment lap
void PhysicsWorld::updateCheckpoint()
{
	Checkpoint c = checkpoints[currentCheckpoint];

	// debug info
	//cout << "Checkpoint " << currentCheckpoint << ": (" << c.x1 << "," << c.z1 << ") and (" << c.x2 << "," << c.z2 << "); Your location: (" << player->getX() << "," << player->getZ() << ")" << endl;


	currentCheckpoint++;
	currentCheckpoint %= 13;
	// flash some kind of message on screen
			
	if (currentCheckpoint == 1)
	{
		currentLap++;
		//lapTimer->setDuration(25, timer->getLaps().back());
		
		timer->saveTimes(lapTimer->getSeconds());
		if (RACE_MODE == 0)
		{
			if (timer->getLaps().back() < ghost->getTotal())
			{
				vector <double> temp;
				temp.push_back(timer->getLaps().back());
				
				record->saveGhost(timer->getLaps().back(), temp);
			}
			record->clearData();
			ghost->openGhost("laprec");
		}
		else if (currentLap > PhysicsWorld::LAP_COUNT)
		{
			if (timer->getTotal() < ghost->getTotal())
			{
				record->saveGhost(timer->getTotal(), timer->getLaps());
			}
		}
		lapTimer->startTimer();
		
		// display lap times
	}
	
	Checkpoint::duration = 25;
}

void PhysicsWorld::spawnPlayer()
{
	player = new Teapot(world, -9.0, -11.0, 0.0);
	player->setColor(0.8, 0.1, 0.1, 1.0);
}

void PhysicsWorld::startGame()
{
	track = new Track(world);

	if (RACE_MODE == 1)
	{
		//ghost = new Ghostpot(world, -9.0, -11.0, 0.0, "racerec");
		ghost->openGhost("racerec");
		record = new GhostRecord("racerec");
		ghost->setColor(0.1, 0.5, 0.1, 1.0);
	}
	else if (RACE_MODE == 0)
	{
		//ghost = new Ghostpot(world, -9.0, -11.0, 0.0, "laprec");
		ghost->openGhost("laprec");
		record = new GhostRecord("laprec");
		ghost->setColor(0.7, 0.7, 0.0, 1.0);
	}

	spawnPlayer();

	timer = new Timer(1.0, 3.0, false);
	lapTimer = new Timer(15.0, 3.0, true);
	
	isPlaying = true;
	
	timer->startTimer();
	lapTimer->startTimer();
	
	
}

// called in main
void PhysicsWorld::splashScreen()
{
	track = new Track(world);
	
	if (RACE_MODE == 1)
	{
		ghost = new Ghostpot(world, -9.0, -11.0, 0.0, "racerec");
		ghost->setColor(0.1, 0.5, 0.1, 1.0);
	}
	else if (RACE_MODE == 0)
	{
		ghost = new Ghostpot(world, -9.0, -11.0, 0.0, "laprec");
		ghost->setColor(0.7, 0.7, 0.0, 1.0);
	}

	isPlaying = false;
}
	

void PhysicsWorld::step(float32 timeStep, int32 iterations, int32* keys)
{	
	if (!isPlaying)
	{
		if (keys[' '])
		{
			isPlaying = true;
			startGame();
			return;
		}
	
		glMatrixMode(GL_PROJECTION);

			// save previous matrix which contains the 
			//settings for the perspective projection
		glPushAttrib(GL_ALL_ATTRIB_BITS);
		glPushMatrix();
			//glDisable(GL_LIGHTING);
			//glEnable(GL_CULL_FACE);
		
			// reset matrix
			glLoadIdentity();
		
			// set a 2D orthographic projection, width = height = 40
			glOrtho(0.0, 40.0, 0.0, 30.0, -10.0, 10.0);
			
			glMatrixMode(GL_MODELVIEW);
			glLoadIdentity();
			
			// rotates the track around on the screen
			glPushMatrix();
				glTranslatef(19.0, 14.0, 0.0);
				glRotatef(rotate, 0.0, 0.0, -1.0);
				glRotatef(90.0, 1.0, 0.0, 0.0);
				
				glPushMatrix();
					glScalef(0.9, 1, 0.9);
					
					track->draw();
					glPushMatrix();
						glTranslatef(0.0, 1.0, 0.0);
						ghost->update();
						ghost->draw(2.5);
					glPopMatrix();
					
					glPushMatrix();
						glLoadIdentity();
						glTranslatef(0.0, 0.0, 7.0);
						glColor3f(1.0, 1.0, 1.0);
						glLineWidth(3.0);
						glScalef(3.0, 3.0, 3.0);
						glColor3f(0.0, 0.0, 0.0);
						drawText(1.55,-4.95, "Teapot Racing!");
						glTranslatef(0.0, 0.0, 1.0);
						glColor3f(1.0, 1.0, 1.0);
						drawText(1.5,-5.0, "Teapot Racing!");
						
						glScalef(0.4, 0.4, 0.4);
						glLineWidth(1.6);
						drawText(3.2,-3.0, "Press <space> to heat up the tea!");
					glPopMatrix();
					
				glPopMatrix();
			glPopMatrix();
	
			glMatrixMode(GL_PROJECTION);
		glPopMatrix();
		glPopAttrib();
		glMatrixMode(GL_MODELVIEW);
		
		//rotate += 0.5;
		
		return;
	}

	// Set proper turning state based on user input
	if (keys[GLUT_KEY_LEFT] && keys[GLUT_KEY_RIGHT] || !keys[GLUT_KEY_LEFT] && !keys[GLUT_KEY_RIGHT])
	{
		player->turn(Teapot::STRAIGHT);
	}
	else if (keys[GLUT_KEY_LEFT])
	{
		player->turn(Teapot::LEFT);
	}
	else if (keys[GLUT_KEY_RIGHT])
	{
		player->turn(Teapot::RIGHT);
	}
	
	// Set proper acceleration state based on user input
	if (keys[GLUT_KEY_UP] && keys[GLUT_KEY_DOWN] || !keys[GLUT_KEY_UP] && !keys[GLUT_KEY_DOWN])
	{
		player->accelerate(Teapot::COAST);
	}
	else if (keys[GLUT_KEY_UP])
	{
		player->accelerate(Teapot::FORWARD);
	}
	else if (keys[GLUT_KEY_DOWN])
	{
		player->accelerate(Teapot::REVERSE);
	} 
	
	// Update teapot speed, position, etc. using new information
	player->update();
	
	glPushMatrix();
		// Keep the camera behind the teapot
		glRotatef(player->getHeading() * 180.0 / M_PI, 0.0, 1.0, 0.0);
		glTranslatef(-player->getX(), 0.0, -player->getZ());

		// Set light position
		glLightfv(GL_LIGHT0, GL_POSITION, spotPosit0);

		track->draw();
	
		glPushMatrix();
			glTranslatef(0.0, 0.2, 0.0);
	
			glPushMatrix();
				// These are so the debug stuff shows up properly
				glTranslatef(0.0, -0.1, 0.0);
				glRotatef(90, 1, 0, 0);
				
				// Perform step in physics simulation
				world->Step(timeStep, iterations);
				
			glPopMatrix();
	
			// Draw player and ghost
			player->draw();
			
			ghost->update();
			ghost->draw();
			
			for (unsigned int i = 0; i < checkpoints.size(); ++i)
			{
				checkpoints[i].draw();
			}
			
			// Draw time information (and other stuff eventually)
			timer->displayTime();
			lapTimer->displayTime();
			
			///
			glMatrixMode(GL_PROJECTION);

			// save previous matrix which contains the 
			//settings for the perspective projection
			glPushMatrix();
		
			glPushAttrib(GL_ALL_ATTRIB_BITS);
			glDisable(GL_LIGHTING);
			glEnable(GL_CULL_FACE);
		
			// reset matrix
				glLoadIdentity();
			
				// set a 2D orthographic projection, width = height = 40
				gluOrtho2D(0.0, 40.0, 0.0, 30.0);
				
				glMatrixMode(GL_MODELVIEW);
				
				glPushAttrib(GL_ALL_ATTRIB_BITS);
				glColor3f(1.0, 1.0, 1.0);
				glPushMatrix();
					glTranslatef(35.0, 3.0, 0.0);
					
					int steps = 20;//(int)(size * 36);
						
					double size = 4.0 * M_PI / 3.0;
					double radius = 3;
					double startAngle = -M_PI / 6.0;
					
					glLineWidth(3.0);
					
					// Gauge outline
					glBegin(GL_LINE_STRIP);
						for (int i = 0; i < steps; i++)
						{
							double angle = startAngle + size*i / (steps - 1);
		
							glVertex2f(radius * cos(angle), radius * sin(angle));
						}
					glEnd();
					
					// Major ticks
					steps = 5;
					glBegin(GL_LINES);
						for (int i = 0; i < steps; i++)
						{
							double angle = startAngle + size*i / (steps - 1);
		
							glVertex2f(radius * cos(angle), radius * sin(angle));
							glVertex2f((radius - 0.75) * cos(angle), (radius - 0.75) * sin(angle));
						}
					glEnd();
					glPushMatrix();
					static string labels[] = {"0", "25", "50", "75", "100"};
					GLdouble labelStartAngle = 5.0 * M_PI / 6.0;
					// Labels
					glColor4f(1.0, 1.0, 1.0, 0.5);
					glLineWidth(1.6);
					
						for (int i = 0; i < steps; i++)
						{
							double angle = labelStartAngle + size*i / (steps - 1);
							
							glPushMatrix();
								glScalef(0.4, 0.4, 0.4);
								drawText((radius + 1.8) * cos(angle) -0.4*i, (radius + 1.0) * sin(angle), labels[i]);
							glPopMatrix();
						}
					glPopMatrix();
					
					// Minor ticks
					glLineWidth(1.5);
					steps = 4;
					startAngle = 0;
					size = M_PI;
					glBegin(GL_LINES);
						for (int i = 0; i < steps; i++)
						{
							double angle = startAngle + size*i / (steps - 1);
		
							glVertex2f(radius * cos(angle), radius * sin(angle));
							glVertex2f((radius - 0.4) * cos(angle), (radius - 0.4) * sin(angle));
						}
					glEnd();
					
					GLdouble speed = player->getSpeed();
					GLdouble angle = -240.0 * speed / 10.0;
					
					glPushMatrix();
						glColor3f(1.0, 0.0, 0.0);
						glRotatef(120.0 + angle, 0.0, 0.0, 1.0);
						glBegin(GL_QUADS);
							glVertex3f(-0.2, 0.0, 1.0);
							glVertex3f(0.2, 0.0, 1.0);
							glVertex3f(0.1, 2.5, 1.0);
							glVertex3f(-0.1, 2.5, 1.0);
						glEnd();
					glPopMatrix();
					
					// Digital speed
					ostringstream s;
					s << setw(2) << setfill('0') << round(speed * 10);
					
					glPushMatrix();
						glLineWidth(3.0);
						drawText(-0.75, 2.25, s.str());
					glPopMatrix();
					
				glPopMatrix();
				glPopAttrib();
				
				glMatrixMode(GL_MODELVIEW);
				glLoadIdentity();
				
				glTranslatef(4.5, 3.5, 0.0);
				glRotatef(90.0, 1.0, 0.0, 0.0);
				
				glPushMatrix();
					glScalef(.2, .2, .2);
					
					track->draw(false);
					glPushMatrix();
						player->drawSimple(3.0);
						ghost->drawSimple(3.0);
					glPopMatrix();
				glPopMatrix();
				glMatrixMode(GL_PROJECTION);
			glPopMatrix();
			glPopAttrib();
			glMatrixMode(GL_MODELVIEW);
			///
			
			if (Checkpoint::duration > 0)
			{
				Checkpoint::checkpointCleared();
				Checkpoint::duration--;
			}
			
		glPopMatrix();
		
	glPopMatrix();

	// Add x, z, and heading to GhostData
	record->recordGhost(player);
	
	checkpointStatus();

	// We should only do this once, when the race is over
	//record->saveGhost();
	
}

